package v3

import (
	"fmt"
	"log"
	"reflect"
	"time"

	"github.com/gogo/protobuf/proto"
)

type MessageID = uint16
type MessageSeq = uint32

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type MessageHead struct {
	MessageID  MessageID
	MessageSeq MessageSeq
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type IMessage interface {
	proto.Message
	V3()
	Head() MessageHead
	SetMessageSeq(seq MessageSeq)
	ResponseMessageID() MessageID
	Size() int
	MarshalTo([]byte) (int, error)
	Unmarshal([]byte) error
	ResetEx()
	Once() bool
	MinInterval() time.Duration
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type messageProducer func() IMessage
type messageRecycler func(IMessage)

type messageFactory struct {
	id          MessageID
	producer    messageProducer
	recycler    messageRecycler
	once        bool
	minInterval time.Duration
}

func newMessageFactory(id MessageID, producer messageProducer, recycler messageRecycler, once bool, minInterval time.Duration) *messageFactory {
	return &messageFactory{
		id:          id,
		producer:    producer,
		recycler:    recycler,
		once:        once,
		minInterval: minInterval,
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type IMessageFactoryManager interface {
	Register(messageID MessageID, iMsg IMessage, producer messageProducer, recycler messageRecycler, once bool, minInterval time.Duration)
	Produce(id MessageID) (IMessage, error)
	Recycle(iMsg IMessage) error
	ReflectType(id MessageID) reflect.Type
}

type messageFactoryManager struct {
	factories map[MessageID]*messageFactory
	id2Type   map[MessageID]reflect.Type
}

func NewMessageFactoryManager() IMessageFactoryManager {
	m := &messageFactoryManager{
		factories: make(map[MessageID]*messageFactory),
		id2Type:   make(map[MessageID]reflect.Type),
	}
	return m
}

func (m *messageFactoryManager) Register(messageID MessageID, iMsg IMessage, producer messageProducer, recycler messageRecycler, once bool, minInterval time.Duration) {
	rt := reflect.TypeOf(iMsg)
	if rt.Kind() == reflect.Ptr {
		rt = rt.Elem()
	}
	name := rt.Name()

	if producer == nil {
		log.Panicf("register message[%s] factory fail, producer is nil, id=[%v]", name, messageID)
	}

	if recycler == nil {
		log.Panicf("register message[%s] factory fail, recycler is nil, id=[%v]", name, messageID)
	}

	if f, ok := m.factories[messageID]; ok {
		log.Panicf("duplicate message[%s] factory, id=[%v], factory=[%+v]", name, messageID, f)
	}

	m.factories[messageID] = newMessageFactory(messageID, producer, recycler, once, minInterval)

	m.id2Type[messageID] = rt
}

func (m *messageFactoryManager) Produce(id MessageID) (IMessage, error) {
	if factory := m.factories[id]; factory != nil {
		return factory.producer(), nil
	}
	return nil, fmt.Errorf("unsupported message, id=[%v]", id)
}

func (m *messageFactoryManager) Recycle(iMsg IMessage) error {
	messageID := iMsg.Head().MessageID
	if factory := m.factories[messageID]; factory != nil {
		factory.recycler(iMsg)
		iMsg = nil
		return nil
	}
	return fmt.Errorf("unsupported message, id=[%v]", messageID)
}

func (m messageFactoryManager) ReflectType(id MessageID) reflect.Type {
	return m.id2Type[id]
}
